package com.lemon.jvm.hotspot.src.share.vm.classfile;

import com.lemon.jvm.hotspot.src.share.tools.DataTranslate;
import com.lemon.jvm.hotspot.src.share.tools.Stream;
import com.lemon.jvm.hotspot.src.share.vm.intepreter.BaseBytecodeStream;
import com.lemon.jvm.hotspot.src.share.vm.intepreter.BytecodeStream;
import com.lemon.jvm.hotspot.src.share.vm.oops.*;
import com.lemon.jvm.hotspot.src.share.vm.utilities.AccessFlags;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ClassFileParser {

    private static Logger logger = LoggerFactory.getLogger(ClassFileParser.class);

    private InstanceKlass klass;
    private byte[] content;
    private int index;
    private byte[] u2Arr = new byte[2];
    private byte[] u4Arr = new byte[4];

    public ClassFileParser(InstanceKlass klass, byte[] content) {
        this.klass = klass;
        this.content = content;
    }

    public static InstanceKlass parseClassFile(byte[] content) {
        InstanceKlass klass = new InstanceKlass();
        ClassFileParser classFileParser = new ClassFileParser(klass, content);
        classFileParser.parse();
        return klass;
    }

    private void parse() {
        System.arraycopy(content, 0, klass.getMagic(), 0, klass.getMagic().length);
        System.arraycopy(content, 4, klass.getMinorVersion(), 0, klass.getMinorVersion().length);
        System.arraycopy(content, 6, klass.getMajorVersion(), 0, klass.getMajorVersion().length);
        parseConstantPool();

        // 类的访问权限 2B
        klass.setAccessFlag(getU2());

        //this_class
        klass.setThisClass(getU2());
        klass.setSuperClass(getU2());

        parseInterface();

        parseField();

        parseMethod();

        parseAttributeInfo();
    }

    private void parseMethod() {
        klass.setMethodLength(getU2());
        MethodInfo[] methodInfos = new MethodInfo[klass.getMethodLength()];
        ConstantPool constantPool = klass.getConstantPool();
        for (int i = 0; i < klass.getMethodLength(); i++) {
            MethodInfo methodInfo = new MethodInfo();
            methodInfo.setBelongKlass(klass);
            methodInfo.setAccessFlags(new AccessFlags(getU2()));
            methodInfo.setNameIndex(getU2());
            methodInfo.setDescriptorIndex(getU2());
            methodInfo.setAttributesCount(getU2());
            String methodName = constantPool.getNameByNameIndex(methodInfo.getNameIndex());
            methodInfo.setMethodName(methodName);

            // 解析方法属性
            if (1 != methodInfo.getAttributesCount()) {
                throw new Error("方法的属性不止一个");
            }
            CodeAttributeInfo codeAttributeInfos[] = new CodeAttributeInfo[methodInfo.getAttributesCount()];
            methodInfo.setAttributeInfos(codeAttributeInfos);
            for (int j = 0; j < methodInfo.getAttributesCount(); j++) {
                CodeAttributeInfo codeAttributeInfo = new CodeAttributeInfo();
                codeAttributeInfos[j] = codeAttributeInfo;
                codeAttributeInfo.setAttrNameIndex(getU2());
                codeAttributeInfo.setAttrLength(getU4());
                codeAttributeInfo.setMaxStack(getU2());
                codeAttributeInfo.setMaxLocals(getU2());
                codeAttributeInfo.setCodeLength(getU4());
                BytecodeStream code = new BytecodeStream(methodInfo, codeAttributeInfo);
                fillBytes(code.getCodes());
                codeAttributeInfo.setExceptionTableLength(getU2());
                codeAttributeInfo.setAttributesCount(getU2());

                for (int k = 0; k < codeAttributeInfo.getAttributesCount(); k++) {
                    AttributeInfo attributeInfo = parseAttributeInfo0();
                    String attrName = constantPool.getNameByNameIndex(attributeInfo.getAttrNameIndex());
                    codeAttributeInfo.getAttributes().put(attrName,attributeInfo);
                }

            }
            methodInfos[i] = methodInfo;
        }
        klass.setMethods(methodInfos);
    }

    private void parseAttributeInfo() {
        int counter = getU2();
        for (int i = 0; i < counter; i++) {
            AttributeInfo attributeInfo = parseAttributeInfo0();
            klass.getAttributeInfos().add(attributeInfo);
        }
    }

    private AttributeInfo parseAttributeInfo0() {
        int attributeNameIndex = getU2();
        String attrName = klass.getConstantPool().getNameByNameIndex(attributeNameIndex);
        AttributeInfo attributeInfo = null;
        if(attrName.equals("LineNumberTable")){
            attributeInfo = parseLineNumberTable();
        } else if(attrName.equals("LocalVariableTable")){
            attributeInfo =  parseLocalVariableTable();
        } else if(attrName.equals("SourceFile")){
            attributeInfo = parseSourceFile();
        }
        attributeInfo.setAttrNameIndex(attributeNameIndex);
        return attributeInfo;
    }

    private AttributeInfo parseSourceFile() {
        AttributeInfo attributeInfo = new AttributeInfo();
        attributeInfo.setAttrLength(getU4());
        byte[] bytes = new byte[2];
        fillBytes(bytes);
        attributeInfo.setContainer(bytes);
        return  attributeInfo;
    }

    private AttributeInfo parseLocalVariableTable() {
        LocalVariableTable localVariableTable = new LocalVariableTable();
        localVariableTable.setAttrLength(getU4());
        localVariableTable.setTableLength(getU2());
        LocalVariableTable.Item[] items = new LocalVariableTable.Item[localVariableTable.getTableLength()];
        for (int i = 0; i < items.length; i++) {
            LocalVariableTable.Item item = localVariableTable.new Item();
            item.setStartPc(getU2());
            item.setLength(getU2());
            item.setNameIndex(getU2());
            item.setDescriptorIndex(getU2());
            item.setIndex(getU2());
            items[i] = item;
        }
        localVariableTable.setTable(items);
        return localVariableTable;
    }

    private AttributeInfo parseLineNumberTable() {
        LineNumberTable lineNumberTable = new LineNumberTable();
        lineNumberTable.setAttrLength(getU4());
        lineNumberTable.setTableLength(getU2());
        LineNumberTable.Item[] items = new LineNumberTable.Item[lineNumberTable.getTableLength()];
        for (int i = 0; i < lineNumberTable.getTableLength(); i++) {
            items[i] =  lineNumberTable.new Item();
            items[i].setStartPc(getU2());
            items[i].setLineNumber(getU2());
        }
        lineNumberTable.setTable(items);
        return lineNumberTable;
    }

    private void parseField() {
        int count = getU2();
        klass.setFieldsLength(count);
        for (int i = 0; i < count; i++) {
            FieldInfo fieldInfo = new FieldInfo();
            fieldInfo.setAccessFlags(getU2());
            fieldInfo.setNameIndex(getU2());
            fieldInfo.setAttributesCount(getU2());
            for (int j = 0; j < fieldInfo.getAttributesCount(); j++) {

            }
        }
    }

    private void parseInterface() {
        int count = getU2();
        klass.setInterfacesLength(count);
        for (int i = 0; i < count; i++) {
            int index = getU2();

        }
    }

    private int getU4() {
        System.arraycopy(content, index, u4Arr, 0, 4);
        index += 4;
        return DataTranslate.byteToInt(u4Arr);
    }

    private int getU2() {
        System.arraycopy(content, index, u2Arr, 0, 2);
        index += 2;
        return DataTranslate.byteToUnsignedShort(u2Arr);
    }

    private void fillBytes(byte[] dest) {
        System.arraycopy(content, index, dest, 0, dest.length);
        index += dest.length;
    }

    private void parseConstantPool() {
        int size = DataTranslate.byteToUnsignedShort(content, 8);
        index = 10;
        ConstantPool constantPool = new ConstantPool(size);
        for (int i = 1; i < size; i++) {
            int tag = content[index++];
            switch (tag) {
                case ConstantPool.JVM_CONSTANT_Utf8: {
                    Stream.readU2Simple(content, index, u2Arr);
                    index += 2;
                    int len = DataTranslate.byteToUnsignedShort(u2Arr);
                    byte[] copy = new byte[len];
                    System.arraycopy(content, index, copy, 0, len);
                    index += len;
                    constantPool.add(i, tag, DataTranslate.bytesToUtf8String(copy));
                    break;
                }
                case ConstantPool.JVM_CONSTANT_Class:
                case ConstantPool.JVM_CONSTANT_String: {
                    //String index
                    Stream.readU2Simple(content, index, u2Arr);
                    index += 2;
                    int stringIndex = DataTranslate.byteToUnsignedShort(u2Arr);
                    constantPool.add(i, tag, stringIndex);
                    break;
                }
                case ConstantPool.JVM_CONSTANT_InterfaceMethodref:
                case ConstantPool.JVM_CONSTANT_NameAndType:
                case ConstantPool.JVM_CONSTANT_Fieldref:
                case ConstantPool.JVM_CONSTANT_Methodref: {
                    // Class_info
                    Stream.readU2Simple(content, index, u2Arr);
                    index += 2;
                    int classIndex = DataTranslate.byteToUnsignedShort(u2Arr);

                    // NameAndType info
                    Stream.readU2Simple(content, index, u2Arr);
                    index += 2;
                    int nameAndTypeIndex = DataTranslate.byteToUnsignedShort(u2Arr);

                    constantPool.add(i, tag, classIndex << 16 | nameAndTypeIndex);
                    break;
                }
                default:
                    throw new Error("无法识别的常量池项");
            }
        }
        klass.setConstantPool(constantPool);
    }


}
